package edu.washington.apl.weather;

import edu.washington.apl.weather.packet.AnenometerPacket;
import edu.washington.apl.weather.packet.IndoorPacket;
import edu.washington.apl.weather.packet.OutdoorPacket;
import org.apache.log4j.Logger;
import edu.washington.apl.weather.packet.Packet;
import edu.washington.apl.weather.packet.PacketDecoder;
import edu.washington.apl.weather.packet.RainPacket;
import edu.washington.apl.weather.util.WeatherProperties;

import java.sql.Connection;

import java.io.InputStream;
import java.util.logging.Level;
import jssc.SerialPort;
import jssc.SerialPortEvent;
import jssc.SerialPortEventListener;

public class WMR968 extends WeatherStation implements SerialPortEventListener, Runnable {

    // make them members so we can close them with shutdown signal hook
    private SerialPort port = null;
    private InputStream stream = null;
    private Connection connection = null;
    private PacketDecoder decoder = null;
    private static Logger logger = Logger.getLogger(WMR968.class);
    WeatherProperties properties;
    public WMR968() {        
        properties = WeatherProperties.getInstance();
        Runtime.getRuntime().addShutdownHook(new Thread(this));
        decoder = new PacketDecoder();
        try {
            port = new SerialPort(properties.getProperty("station.wmr968.comport", "COM1"));
            port.openPort();

            // default settings
            port.setParams(9600,8,1,0);//, humidity, humidity, humidity)setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
            port.addEventListener(this);
        } catch (Exception e) {
            java.util.logging.Logger.getLogger(WMR968.class.getName()).log(Level.SEVERE, "ERROR Setting Up Ports & Streams", e);
            System.exit(0);
        }

    }
    // packet buffer and length
    private byte[] buffer = new byte[128];
    private int bindex = 0;
    // single packet and length
    private byte[] packet = new byte[128];
    private int plength = 0;
    // length of validated packet
    private int length = 0;

    /**
     * Parses a completed packet from the buffer. If no packet is available,
     * this method will return null.
     *
     * @return
     */
    private byte[] parsePacketFromBuffer() {
        // begin looking for a start of frame.
        int index = 0;
        int sof = 0;
        boolean sofFound = false;
        for (index = 0; index < (buffer.length - 1); index++) {
            if ((buffer[index] == -1) && (buffer[index + 1] == -1)) {
                sof = index;
                sofFound = true;
                index++;
                break;
            }
        }

        if (sofFound == false) {
            return null;
        }

        // attempt to find the start of the next frame. we will use this as a
        // signal to determine where our current frame ends
        for (; index < (buffer.length - 1); index++) {
            if ((buffer[index] == -1) && (buffer[index + 1] == -1)) {
                // determine the length of the frame by taking the current index
                // and subtracting the start of frame position.
                int len = index - sof;

                // create a new array to hold the parsed frame.
                byte[] array = new byte[len];

                // parse
                System.arraycopy(buffer, sof, array, 0, len);

                // now, shift the balance of the array left to 0
                for (index = 0; index < (buffer.length - len); index++) {
                    buffer[index] = buffer[sof + len + index];
                }
                bindex = bindex - len;
                // 0 fill the rest of the array
                for (; index < buffer.length; index++) {
                    buffer[index] = 0;
                }
                //bindex = bindex-sof+len;
                return array;
            }
        }
        return null;
    }

    @Override
    public void serialEvent(SerialPortEvent e) {
        //if (e.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
            try {
                packet = port.readBytes();
                //System.err.println("rx["+plength+"]: " + Arrays.toString(packet));

                if ((bindex + packet.length) > 128) {
                    System.err.println("buffer overflow, resetting buffer");
                    bindex = 0;
                    return;
                }

                // check to see how much data we got in the previous read and place 
                // it in the buffer.
                System.arraycopy(packet, 0, buffer, bindex, packet.length);
                bindex += packet.length;

                // now, we need to scan the buffer for completed packets
                byte[] frame = parsePacketFromBuffer();
                while (frame != null) {
                    processResponse(frame);
                    frame = parsePacketFromBuffer();
                }

            } catch (Exception exp) {
                logger.error("Problem With Packet", exp);
                return;
            }
       // }
    }

    private void processResponse(byte[] frame) {
        Packet p;

        // if we get a valid packet we want to save the length and clear the buffer
        if (Packet.checksum(frame) == false) {
            System.err.println("Checksum error in packet.");
            return;                    
        }

        p = decoder.decode(frame);
        // update the state of the class based the type of the packet
        if (p instanceof IndoorPacket) {
            IndoorPacket idp = (IndoorPacket) p;
            setIndoorTemp(toF(idp.getTemp()*idp.getTempSign()));
            setIndoorDewpoint(toF(idp.getDewpoint()));
            setIndoorHumidity(idp.getHumidity());
            setBarometricPressure(toInches(idp.getPressure()));
        } else if (p instanceof OutdoorPacket) {
            OutdoorPacket odp = (OutdoorPacket) p;
            setOutdoorTemp(toF(odp.getTemp()*odp.getTempSign()));
            setOutdoorDewpoint(toF(odp.getDewpoint()));
            setOutdoorHumidity(odp.getHumidity());
        } else if (p instanceof AnenometerPacket) {
            AnenometerPacket anp = (AnenometerPacket) p;
            setWindGust(fromMsToMph(anp.getGustSpeed()));
            setWindAvg(fromMsToMph(anp.getAvgSpeed()));
            setWindDir(anp.getBearing());
        } else if (p instanceof RainPacket) {
            RainPacket anp = (RainPacket) p;
            setRainRate(anp.getRate());
            setRainTotal(anp.getTotal());
        }
        try {
        getUpdateListener().weatherEvent(this,System.currentTimeMillis());
        } catch (Exception ex) {
            java.util.logging.Logger.getLogger(WMR968.class.getName()).log(Level.INFO, ex.getMessage(), ex);
        }
    }
    
    // runnable method for cleaning up when shutdown initiated
    public void run() {
        logger.info("Closing Databases, Streams, & Ports ...");

        try {
            if (stream != null) {
                stream.close();
            }
            if (port != null) {
                port.closePort();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (Exception e) {
            logger.error("ERROR Closing Databases, Streams, & Ports", e);
        }

        logger.info("All closed.");
    }
    @Override
    public String transformData() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
}
